package org.locker.netty;

import cn.hutool.json.JSONUtil;
import com.baomidou.lock.annotation.Lock4j;
import io.netty.channel.Channel;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import io.netty.util.HashedWheelTimer;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;
import org.locker.common.core.config.AsyncConfig;
import org.locker.common.core.utils.StringUtils;
import org.locker.netty.dto.MessageEnum;
import org.locker.netty.dto.MessageObject;
import org.locker.netty.dto.command.*;
import org.locker.netty.dto.reply.CommonReply;
import org.locker.netty.dto.reply.GetSimInfoReply;
import org.locker.netty.dto.reply.QueryApkLastVersionD2sReply;
import org.locker.netty.dto.reply.SynchronousGridListReply;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.util.Iterator;
import java.util.Map;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @description
 * @author: 徐子木
 * @create: 2020-09-30 15:06
 **/
@Slf4j
@Service(value = "messageService")
public class MessageService {

    private volatile String previousKey = null;  // 上一个键

    @Lazy
    @Autowired
    private MachineService machineService;

    @Autowired
    private ScheduledExecutorService scheduledExecutorService;

    // 这里是netty可为指定的唯一key去与连接进行分组处理并存储
    private HashedWheelTimer hashedWheelTimer = new HashedWheelTimer();
    /**
     * 设备ID
     */
    private final AttributeKey<String> deviceKey = AttributeKey.valueOf("deviceKey");


    /**
     * 场地ID
     */
    private final AttributeKey<String> positionKey = AttributeKey.valueOf("positionKey");

    /**
     * 装载标段与对应在线的用户
     */
    private static final Map<String, ChannelGroup> SECTION_GROUPS = new ConcurrentHashMap<>();


    /**
     * 维护某标段中的socket连接
     *
     * @param positionId
     * @param channel
     */

    public void putConnection(String deviceNo, String positionId, Channel channel) {

        channel.attr(deviceKey).set(deviceNo);
        channel.attr(positionKey).set(positionId);


        //存储用户标段对应连接
        ChannelGroup channelGroup = SECTION_GROUPS.get(positionId);
        if (null == channelGroup) {
            //保存全局的,连接上的服务器的客户
            channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
            channelGroup.add(channel);
            SECTION_GROUPS.put(positionId, channelGroup);
        } else {
            channelGroup.add(channel);
        }
    }

    /**
     * 判断一个通道是否有用户在使用
     *
     * @param channel
     * @return
     */
    public boolean hasDevice(Channel channel) {
        return ((channel.hasAttr(deviceKey) || channel.hasAttr(positionKey)));
    }

    /**
     * 获取连接对应用户
     *
     * @param channel
     * @return
     */

    public String getBindDeviceNo(Channel channel) {
        if (hasDevice(channel)) {
            return channel.attr(deviceKey).get();
        }
        return null;
    }

    /**
     * 获取连接对应标段Id
     *
     * @param channel
     * @return
     */

    public String getBindPositionKey(Channel channel) {
        if (hasDevice(channel)) {
            return channel.attr(positionKey).get();
        }
        return null;
    }


    /**
     * 用户退出标段在线连接
     *
     * @param channel
     */

    public void removeConnection(Channel channel) {
        String deviceNo = getBindDeviceNo(channel);
        String positionKey = getBindPositionKey(channel);

        Iterator<Map.Entry<String, ChannelGroup>> iterator = SECTION_GROUPS.entrySet().iterator();
        while (iterator.hasNext()) {
            ChannelGroup channelGroup = iterator.next().getValue();
            if (channelGroup.contains(channel)) {
                channelGroup.remove(channel);
            }
            if (null == channelGroup || channelGroup.size() == 0) {
                iterator.remove();
            }
        }
        if (StringUtils.isNotEmpty(deviceNo)) {
            // 设备退出
            machineService.logout(deviceNo);
        }
        channel.disconnect();
    }

    /**
     * 根据用户Id获取连接
     *
     * @param deviceNo
     * @return
     */
    private Channel getChannel(String deviceNo) {
        Iterator<Map.Entry<String, ChannelGroup>> iterator = SECTION_GROUPS.entrySet().iterator();
        while (iterator.hasNext()) {
            ChannelGroup channelGroup = iterator.next().getValue();
            for (Channel channel : channelGroup) {
                if (deviceNo.equalsIgnoreCase(channel.attr(deviceKey).get())) {
                    return channel;
                }
            }
        }
        return null;
    }


    /**
     * 向指定投标人发送状态
     *
     * @param toDeviceNo
     */
    @Lock4j(keys = {"#toDeviceNo"}, expire = 3000, acquireTimeout = 5000)
    public boolean sendMessage(String toDeviceNo, MessageObject messageObject) {
        // 如果当前键与上一个键相同，执行sleep操作，避免N个不同设备来，都需要sleep避免损耗
        if (previousKey == null || previousKey.equals(toDeviceNo)) {
            try {
                // 执行sleep操作
                //避免粘包
                Thread.sleep(500);
            } catch (InterruptedException e) {
                // 处理中断异常
                e.printStackTrace();
            }
        }
        previousKey = toDeviceNo;

        Channel channel = getChannel(toDeviceNo);
        if (channel != null) {
            //心跳单独处理。不验证签名等
            String msg = messageObject.toHeartMsg();
            if (!messageObject.getCode().equals(MessageEnum.HEART)) {
                messageObject.setDeviceNo(toDeviceNo);
                msg = messageObject.toMsg();
            }
            log.info("【SUCCESS】---sendMessage------>{}-------->{},{}", toDeviceNo, messageObject.getCode(), messageObject.getData());
            channel.writeAndFlush(msg);
            return true;
        } else {
            String msg = messageObject.toMsg();
            log.error("【FAIL】---sendMessage---【FAIL】--->{}---NOT CONNECT----->{},{}", toDeviceNo, messageObject.getCode(), messageObject.getData());
        }
        return false;
    }

    public void sendMessageAll(String positionKey, MessageObject messageObject) {
        ChannelGroup channelGroup = SECTION_GROUPS.get(positionKey);
        if (channelGroup == null || channelGroup.size() == 0) {
            log.warn("暂时无客户端在线 positionKey：{}", positionKey);
            return;
        }
        channelGroup.writeAndFlush(messageObject.toMsg());
    }


    /**
     * 处理设备端发来的消息
     *
     * @param message
     */
    public void dealMessage(MessageObject message) {
        MessageObject.MessageObjectBuilder builder = MessageObject.builder();
        MessageEnum code = message.getCode();
//        如果是心跳就不处理
        if (code.equals(MessageEnum.HEART)) {

            return;
        }

        String deviceNo = message.getDeviceNo();
        if (StringUtils.isEmpty(deviceNo) || !message.verify()) {
            log.info("=====>{}====deviceNo:{}====>verify fail", code, deviceNo);
            //验证不通过直接断掉连接
            removeConnection(NettyContext.getChannel());
        }

        if (NettyContext.getChannel().hasAttr(deviceKey)) {
            Attribute<String> deviceNoKey = NettyContext.getChannel().attr(deviceKey);
            deviceNo = deviceNoKey.get();
        }


        String finalDeviceNo = deviceNo;
        switch (code) {
            case LOGIN_D2S -> {
                LoginDevice loginDevice = (LoginDevice) message.getData().toBean(code.getClazz());
                Long positionId = machineService.loginD2s(loginDevice.getDeviceNo(), loginDevice.getDevicePwd());
                CommonReply commonReply = new CommonReply();
                builder.code(MessageEnum.LOGIN_D2S_REPLY);
                if (positionId != null) {
                    commonReply.result = true;
                    commonReply.setMsg("verify ok");
                    log.info("=====>{}====deviceNo:{}=====positionId:{}====>verify ok", code, deviceNo, positionId);
                    putConnection(deviceNo, positionId + "", NettyContext.getChannel());
                    builder.data(JSONUtil.parseObj(commonReply));
                    MessageObject messageObject = builder.build();
                    sendMessage(deviceNo, messageObject);

                    TimerTask task = new TimerTask() {
                        @Override
                        public void run() {
                            machineService.setOnline(finalDeviceNo);
                        }
                    };

                    //60秒后继续执行设置设备在线。避免设备离线，然后连上来。心跳判断30秒后触发。导致出现设置离线
                    scheduledExecutorService.schedule(task, 60L, TimeUnit.SECONDS);

                } else {
                    log.error("=====>{}====deviceNo:{}=====positionId:{}====>verify FAIL", code, deviceNo, positionId);
                    commonReply.result = false;
                    commonReply.setMsg("verify fail");
                }

                if (positionId == null) {
                    removeConnection(NettyContext.getChannel());
                }
            }

            case HEART -> {
//                CommonReply commonReply = new CommonReply();
//                commonReply.result = true;
//                commonReply.setMsg("ping");
//                builder.code(MessageEnum.HEART);
//                builder.data(JSONUtil.parseObj(commonReply));
//                MessageObject messageObject = builder.build();
//                sendMessage(deviceNo, messageObject);
            }
            case SET_DEVICE_POSITION_REPLY -> {
                CommonReply commonReply = new CommonReply();
                MessageObject messageObject = builder.build();

            }


            case CONTROL_OPEN_ALL_REPLY -> {

            }

            case SYNCHRONOUS_GRID_LIST_REPLY -> {
                SynchronousGridListReply reply = (SynchronousGridListReply) message.getData().toBean(code.getClazz());
                boolean synchronoused = machineService.synchronousGridListReply(deviceNo, reply);

            }

            case BATCH_SET_GRID_REPLY -> {

            }

            case DEVICE_COMMON_INFO_SYNC_REPLY -> {

            }

            case CONTROL_BLE_SWITCH_REPLY -> {

            }
            case UPGRADE_BLE_REPLY -> {

            }
            case PUSH_ORDER_REPLY -> {

            }
            case CONTROL_OPEN_REPLY -> {


            }
            case PUSH_ORDER_FINISH_REPLY -> {

            }
            case ORDER_LIST_SYNC_REPLY -> {


            }

            case GET_SIM_INFO_REPLY -> {
                GetSimInfoReply reply = (GetSimInfoReply) message.getData().toBean(code.getClazz());
                boolean synchronoused = machineService.getSimInfoReply(deviceNo, reply);

            }
            case ADMIN_EXEC_REPLY -> {

            }
            case MODIFY_ADMIN_ACCOUNT_REPLY -> {


            }
            case UPLOAD_DEVICE_LOG_REPLY -> {


            }
            case DELETED_DEVICE_LOG_REPLY -> {


            }
            case DEVICE_SINGLE_LOCK_STATUS_REPLY -> {
                //锁信息服务端回复
                DeviceSingleLockStatusReply reply = (DeviceSingleLockStatusReply) message.getData().toBean(code.getClazz());
                boolean synchronoused = machineService.deviceSingleLockStatusReply(deviceNo, reply);
            }
            case OFFLINE_ORDER_PUSH_SYNC_D2S -> {
                //收到指令处理
                PushOrder reply = (PushOrder) message.getData().toBean(code.getClazz());
                //发送指令
                PushOrder pushOrder = machineService.offlineOrderPushSyncD2s(reply);
                machineService.offlineOrderPushSyncD2sReply(pushOrder);
            }
            case OFFLINE_ORDER_PUSH_SYNC_D2S_REPLY -> {

            }
            case QUERY_APK_LAST_VERSION_D2S -> {
                QueryApkLastVersionD2s reply = (QueryApkLastVersionD2s) message.getData().toBean(code.getClazz());
                //收到指令处理
                QueryApkLastVersionD2sReply versionD2sReply = machineService.queryApkLastVersionD2s(reply);
                //发送指令
                machineService.queryApkLastVersionD2sReply(deviceNo, versionD2sReply);
            }
            case QUERY_APK_LAST_VERSION_D2S_REPLY -> {


            }

            case QUERY_ORDER_LIST_SYNC_D2S -> {
                boolean synchronoused = machineService.queryOrderListSyncD2sReply(deviceNo);

            }
            case QUERY_ORDER_LIST_SYNC_D2S_REPLY -> {


            }
            case DEVICE_CONTROL_OPEN_RESULT_D2S -> {
                ControlOpenResult controlOpenResult = (ControlOpenResult) message.getData().toBean(code.getClazz());
                try {
                    Thread.sleep(500L);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                boolean synchronoused = machineService.deviceControlOpenResultD2s(controlOpenResult);


            }
        }

    }


}

